#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <strings.h>

#include "common.h"
#include "cache.h"

#include <assert.h>

#define MK_ADDR(c, t, i, b)	((((t) << (c)->tag_shift) & (c)->tag_mask) | \
							(((i) << (c)->index_shift) & (c)->index_mask) | \
							((b) & (c)->byte_mask))

int main(int argc, char *argv[]) {
	// 16 K L1 cache
	CACHE L1 = { .geom = {	.idx	= 0,	// 64x2^0 = 64 index positions
							.line	= 5,	// 2x2^5  = 64 bytes per line
							.assoc	= 3 }};	// 3+1    = 4-way associative
	
	// 256 K L2 cache
	CACHE L2 = { .geom = {	.idx	= 3,	// 64x2^1 = 512 index positions
							.line	= 6,	// 2x2^6  = 128 bytes per line
							.assoc	= 3 }};	// 3+1    = 4-way associative
	
	// 8 M L3 cache
	CACHE L3 = { .geom = {	.idx	= 6,	// 64x2^6 = 4096 index positions
							.line	= 10,	// 2x2^10 = 2048 bytes per line
							.assoc	= 0 }};	// 0+1    = 1-way associative
	
	assert(create_cache(&L1) == E_OKAY);
	assert(create_cache(&L2) == E_OKAY);
	assert(create_cache(&L3) == E_OKAY);
	
	// L1 cache tests:
	//           63                      11            5        0
	// ADDRESS: |          TAG          |    INDEX    |  BYTES  |
	//           63              13      11            5        0
	// INDEX:   |     Unused    |  WAY  |    INDEX    |  BYTES  |

	assert(L1.ways == 4);
	assert(L1.way_mask == 0x3000);
	assert(L1.way_shift == 12);

	assert(L1.tag_mask == 0xFFFFFFFFFFFFF000LL);
	assert(L1.tag_shift == 12);
	assert(L1.index_mask == 0xFC0);
	assert(L1.index_shift == 6);

	assert(L1.byte_mask == 0x3F);
	assert(L1.bytes == 64);
	assert(L1.indices == 64);

	cache_addr addr1 = MK_ADDR(&L1, 0x1, 0xa0, 0x0);
	cache_addr addr2 = MK_ADDR(&L1, 0x2, 0xa0, 0x0);
	cache_addr addr3 = MK_ADDR(&L1, 0x3, 0xa0, 0x0);
	cache_addr addr4 = MK_ADDR(&L1, 0x4, 0xa0, 0x0);
	cache_addr addr5 = MK_ADDR(&L1, 0xb, 0xa0, 0x0);
	cache_addr addr6 = MK_ADDR(&L1, 0xb, 0xa0, 0x3C);
	
	cache_addr tag1  = 0x0;
	uint8_t buf1[64], buf2[64];
	
	int i;
	for(i=0; i<64; i++)
		buf1[i] = i;


	for(i=0; i<64; i++)
		buf2[i] = 0xFF;
	
	cache_addr wb;
	
	// Fill index 0xa0
	assert(cache_insert_addr(&L1, addr1, buf2, NULL) == E_OKAY);
	assert(cache_insert_addr(&L1, addr2, buf2, NULL) == E_OKAY);
	assert(cache_insert_addr(&L1, addr3, buf2, NULL) == E_OKAY);
	assert(cache_insert_addr(&L1, addr4, buf2, NULL) == E_OKAY);

	uint8_t nbyte;
	
	// Verify data
	assert(cache_read_addr(&L1, addr1+10, NULL, 1, &nbyte) == E_CACHE_HIT);
	assert(nbyte = 0xff);

	nbyte = 0xaa;

	// Make all lines dirty
	assert(cache_write_addr(&L1, addr1+10, NULL, 1, &nbyte) == E_CACHE_HIT);
	assert(cache_write_addr(&L1, addr2+10, NULL, 1, &nbyte) == E_CACHE_HIT);
	assert(cache_write_addr(&L1, addr3+10, NULL, 1, &nbyte) == E_CACHE_HIT);
	assert(cache_write_addr(&L1, addr4+10, NULL, 1, &nbyte) == E_CACHE_HIT);

	// Reverify data
	assert(cache_read_addr(&L1, addr1+10, NULL, 1, &nbyte) == E_CACHE_HIT);
	assert(nbyte = 0xaa);

	// This should require a writeback, since its been the longest since we
	// queried addr2 that will be what we write back
	assert(cache_insert_addr(&L1, addr5, buf1, &wb) == E_CACHE_WRITEBACK);
	assert(wb == addr2);

	assert(cache_read_addr(&L1, addr5+10, NULL, 1, &nbyte) == E_CACHE_MISS);
	
	assert(cache_open_writeback(&L1, addr2, NULL) == E_OKAY);
	assert(cache_write_addr(&L1, addr2, NULL, 1, &nbyte) == E_CACHE_BUSY);
	assert(cache_abort_writeback(&L1, addr2) == E_OKAY);

	nbyte = 0xbb;
	assert(cache_write_addr(&L1, addr2, NULL, 1, &nbyte) == E_CACHE_HIT);
	assert(cache_read_addr(&L1, addr2, NULL, 1, &nbyte) == E_CACHE_HIT);
	assert(nbyte == 0xbb);

	assert(cache_insert_addr(&L1, addr5, buf1, &wb) == E_CACHE_WRITEBACK);

	// Since we changed addr2
	assert(wb == addr3);
	assert(cache_open_writeback(&L1, addr3, NULL) == E_OKAY);
	assert(cache_close_writeback(&L1, addr3) == E_OKAY);
	assert(cache_insert_addr(&L1, addr5, buf1, &wb) == E_OKAY);

	return 0;
}

